{
 "cells": [
  {
   "cell_type": "markdown",
   "source": [
    "# 2.1. 数据操作\n",
    ":label:`sec_ndarray`\n",
    "\n",
    "为了能够完成各种操作，我们需要某种方法来存储和操作数据。一般来说，我们需要做两件重要的事情：（1）获取数据；（2）在数据读入计算机后对其进行处理。如果没有某种方法来存储数据，那么获取数据是没有意义的。我们先尝试一个合成数据。首先，我们介绍$n$维数组，也称为 *张量*（tensor）。\n",
    "\n",
    "如果你使用过 Python 中最广泛使用的科学计算包 NumPy，那么你会感觉对本部分很熟悉。无论你使用哪个框架，它的 *张量类*（在 MXNet 中为 `ndarray`，在 Paddle中为 `Tensor`）与 Numpy 的 `ndarray` 类似，但都比Numpy 的 `ndarray`多一些重要功能。首先，GPU 很好地支持加速计算，而 NumPy 仅支持 CPU 计算。其次，张量类支持自动微分。这些功能使得张量类更适合深度学习。除非另有说明，在整本书中所说的张量指的是张量类的实例。\n",
    "\n",
    "\n",
    "## 2.1.1. 入门\n",
    "\n",
    "在本节中，我们的目标是帮助你开始了解并运行一些基本数值计算工具。在你阅读本书的过程中，将用到这些工具。如果你很难理解一些数学概念或库函数，请不要担心。在后面的章节将通过一些实际的例子来回顾这些内容。如果你已经有了一些背景知识，想要深入学习数学内容，可以就跳过这一节。\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "(**首先，我们导入 `paddle`。**)\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "source": [
    "import paddle"
   ],
   "outputs": [],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "[**张量表示一个数值组成的数组，这个数组可能有多个维度**]。具有一个轴的张量对应于数学上的 *向量*（vector）。具有两个轴的张量对应于数学上的 *矩阵*（matrix）。具有两个轴以上的张量没有特殊的数学名称。\n",
    "\n",
    "首先，我们可以使用 `arange` 创建一个行向量 `x`。这个行向量包含以0开始的前12个整数，它们默认创建为浮点数。张量中的每个值都称为张量的 *元素*（element）。例如，张量 `x` 中有 12 个元素。除非额外指定，新的张量将存储在内存中，并采用基于CPU的计算。\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "source": [
    "x = paddle.arange(12)\n",
    "x"
   ],
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "Tensor(shape=[12], dtype=int64, place=CPUPlace, stop_gradient=True,\n",
       "       [0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10, 11])"
      ]
     },
     "metadata": {},
     "execution_count": 2
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "[**我们可以通过张量的 `shape` 属性来访问张量的 *形状***] (~~和张量中元素的总数~~)（沿每个轴的长度）。\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "source": [
    "x.shape"
   ],
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "[12]"
      ]
     },
     "metadata": {},
     "execution_count": 3
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "如果我们只想知道张量中元素的总数，即形状的所有元素乘积，我们可以检查它的大小（size）。\n",
    "因为这里在处理的是一个向量，所以它的 `shape` 与它的 `size` 相同。\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "source": [
    "x.numel()"
   ],
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "Tensor(shape=[1], dtype=int64, place=CPUPlace, stop_gradient=True,\n",
       "       [12])"
      ]
     },
     "metadata": {},
     "execution_count": 4
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "[**要改变一个张量的形状而不改变元素数量和元素值，我们可以调用 `paddle.reshape` 函数。**]\n",
    "例如，我们可以把张量 `x` 从形状为 (12, ) 的行向量转换为形状 (3, 4) 的矩阵。这个新的张量包含与转换前相同的值，但是把它们看成一个三行四列的矩阵。要重点说明一下，虽然形状发生了改变，但元素值没有变。注意，通过改变张量的形状，张量的大小不会改变。\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "source": [
    "X = paddle.reshape(x, (3, 4))\n",
    "X"
   ],
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "Tensor(shape=[3, 4], dtype=int64, place=CPUPlace, stop_gradient=True,\n",
       "       [[0 , 1 , 2 , 3 ],\n",
       "        [4 , 5 , 6 , 7 ],\n",
       "        [8 , 9 , 10, 11]])"
      ]
     },
     "metadata": {},
     "execution_count": 5
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "不需要通过手动指定每个维度来改变形状。\n",
    "如果我们的目标形状是 (高度, 宽度) ，那么在知道宽度后，高度应当会隐式得出，我们不必自己做除法。在上面的例子中，要获得一个有3行的矩阵，我们手动指定了它有3行和4列。幸运的是，张量在给出其他部分后可以自动计算出一个维度。我们可以通过将希望张量自动推断的维度放置 `-1` 来调用此功能。在上面的例子中，我们可以用 `paddle.reshape(x, (-1, 4))` 。\n",
    "\n",
    "\n",
    "有时，我们希望[**使用全0、全1、其他常量或者从特定分布中随机采样的数字**]，来初始化矩阵。我们可以创建一个形状为 (2, 3, 4) 的张量，其中所有元素都设置为0。代码如下：\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "source": [
    "paddle.zeros((2, 3, 4))"
   ],
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "Tensor(shape=[2, 3, 4], dtype=float32, place=CPUPlace, stop_gradient=True,\n",
       "       [[[0., 0., 0., 0.],\n",
       "         [0., 0., 0., 0.],\n",
       "         [0., 0., 0., 0.]],\n",
       "\n",
       "        [[0., 0., 0., 0.],\n",
       "         [0., 0., 0., 0.],\n",
       "         [0., 0., 0., 0.]]])"
      ]
     },
     "metadata": {},
     "execution_count": 6
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "同样的，我们可以创建一个张量，其中所有元素都设置为1。代码如下：\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "source": [
    "paddle.ones((2, 3, 4))"
   ],
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "Tensor(shape=[2, 3, 4], dtype=float32, place=CPUPlace, stop_gradient=True,\n",
       "       [[[1., 1., 1., 1.],\n",
       "         [1., 1., 1., 1.],\n",
       "         [1., 1., 1., 1.]],\n",
       "\n",
       "        [[1., 1., 1., 1.],\n",
       "         [1., 1., 1., 1.],\n",
       "         [1., 1., 1., 1.]]])"
      ]
     },
     "metadata": {},
     "execution_count": 7
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "有时我们想从某个概率分布中随机采样来得到张量中每个元素的值。例如，当我们构造数组来作为神经网络中的参数时，我们通常会随机初始化参数的值。以下代码创建一个形状为 (3, 4) 的张量。其中的每个元素都从均值为0、标准差为1的标准高斯（正态）分布中随机采样。\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "source": [
    "paddle.randn((3, 4),'float32')"
   ],
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "Tensor(shape=[3, 4], dtype=float32, place=CPUPlace, stop_gradient=True,\n",
       "       [[ 1.15407705, -0.49682540, -0.19511381,  0.85199744],\n",
       "        [-2.53822398,  0.80129933, -0.08702664, -0.20793132],\n",
       "        [-1.08098662,  2.28351831,  1.56670439, -0.79412812]])"
      ]
     },
     "metadata": {},
     "execution_count": 13
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "我们还可以[**通过提供包含数值的 Python 列表（或嵌套列表）来为所需张量中的每个元素赋予确定值**]。在这里，最外层的列表对应于轴 0，内层的列表对应于轴 1。\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "source": [
    "paddle.to_tensor([[2, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])"
   ],
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "Tensor(shape=[3, 4], dtype=int64, place=CPUPlace, stop_gradient=True,\n",
       "       [[2, 1, 4, 3],\n",
       "        [1, 2, 3, 4],\n",
       "        [4, 3, 2, 1]])"
      ]
     },
     "metadata": {},
     "execution_count": 13
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 2.1.2. 运算\n",
    "\n",
    "这本书不是关于软件工程的。我们的兴趣不仅仅限于从数组读取和写入数据。我们想在这些数组上执行数学运算。一些最简单且最有用的操作是 *按元素*（elementwise） 操作。它们将标准标量运算符应用于数组的每个元素。对于将两个数组作为输入的函数，按元素运算将二元运算符应用于两个数组中的每对位置对应的元素。我们可以基于任何从标量到标量的函数来创建按元素函数。\n",
    "\n",
    "在数学表示法中，我们将通过符号 $f: \\mathbb{R} \\rightarrow \\mathbb{R}$ 来表示 *一元* 标量运算符（只接收一个输入）。这意味着该函数从任何实数（$\\mathbb{R}$）映射到另一个实数。同样，我们通过符号 $f: \\mathbb{R}, \\mathbb{R} \\rightarrow \\mathbb{R}$ 表示 *二元* 标量运算符，这意味着该函数接收两个输入，并产生一个输出。给定同一形状的任意两个向量$\\mathbf{u}$和$\\mathbf{v}$ 和二元运算符 $f$，我们可以得到向量$\\mathbf{c} = F(\\mathbf{u},\\mathbf{v})$。具体计算方法是$c_i \\gets f(u_i, v_i)$ ，其中 $c_i$、$u_i$ 和 $v_i$ 分别是向量$\\mathbf{c}$、$\\mathbf{u}$ 和 $\\mathbf{v}$中的元素。在这里，我们通过将标量函数升级为按元素向量运算来生成向量值 $F: \\mathbb{R}^d, \\mathbb{R}^d \\rightarrow \\mathbb{R}^d$。\n",
    "\n",
    "对于任意具有相同形状的张量，[**常见的标准算术运算符（`+`、`-`、`*`、`/` 和 `**`）都可以被升级为按元素运算**]。我们可以在同一形状的任意两个张量上调用按元素操作。在下面的例子中，我们使用逗号来表示一个具有5个元素的元组，其中每个元素都是按元素操作的结果。\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "source": [
    "x = paddle.to_tensor([1.0, 2, 4, 8])\n",
    "y = paddle.to_tensor([2, 2, 2, 2])\n",
    "x + y, x - y, x * y, x / y, x**y  # **运算符是求幂运算"
   ],
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "(Tensor(shape=[4], dtype=float32, place=CPUPlace, stop_gradient=True,\n",
       "        [3. , 4. , 6. , 10.]),\n",
       " Tensor(shape=[4], dtype=float32, place=CPUPlace, stop_gradient=True,\n",
       "        [-1.,  0.,  2.,  6.]),\n",
       " Tensor(shape=[4], dtype=float32, place=CPUPlace, stop_gradient=True,\n",
       "        [2. , 4. , 8. , 16.]),\n",
       " Tensor(shape=[4], dtype=float32, place=CPUPlace, stop_gradient=True,\n",
       "        [0.50000000, 1.        , 2.        , 4.        ]),\n",
       " Tensor(shape=[4], dtype=float32, place=CPUPlace, stop_gradient=True,\n",
       "        [1. , 4. , 16., 64.]))"
      ]
     },
     "metadata": {},
     "execution_count": 15
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "可以(**按按元素方式应用更多的计算**)，包括像求幂这样的一元运算符。\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "source": [
    "paddle.exp(x)"
   ],
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "Tensor(shape=[4], dtype=float32, place=CPUPlace, stop_gradient=True,\n",
       "       [2.71828175   , 7.38905621   , 54.59814835  , 2980.95800781])"
      ]
     },
     "metadata": {},
     "execution_count": 16
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "除了按元素计算外，我们还可以执行线性代数运算，包括向量点积和矩阵乘法。我们将在 :numref:`sec_linear-algebra` 中解释线性代数的重点内容（不需要先修知识）。\n",
    "\n",
    "[**我们也可以把多个张量 *连结*（concatenate） 在一起**]，把它们端对端地叠起来形成一个更大的张量。\n",
    "我们只需要提供张量列表，并给出沿哪个轴连结。下面的例子分别演示了当我们沿行（轴-0，形状的第一个元素）和按列（轴-1，形状的第二个元素）连结两个矩阵时会发生什么情况。我们可以看到，第一个输出张量的轴-0长度 ($6$) 是两个输入张量轴-0长度的总和 ($3 + 3$)；第二个输出张量的轴-1长度 ($8$) 是两个输入张量轴-1长度的总和 ($4 + 4$)。\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "source": [
    "X = paddle.arange(12, dtype='float32').reshape((3, 4))\n",
    "Y = paddle.to_tensor([[2.0, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])\n",
    "paddle.concat((X, Y), axis=0), paddle.concat((X, Y), axis=1)"
   ],
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "(Tensor(shape=[6, 4], dtype=float32, place=CPUPlace, stop_gradient=True,\n",
       "        [[0. , 1. , 2. , 3. ],\n",
       "         [4. , 5. , 6. , 7. ],\n",
       "         [8. , 9. , 10., 11.],\n",
       "         [2. , 1. , 4. , 3. ],\n",
       "         [1. , 2. , 3. , 4. ],\n",
       "         [4. , 3. , 2. , 1. ]]),\n",
       " Tensor(shape=[3, 8], dtype=float32, place=CPUPlace, stop_gradient=True,\n",
       "        [[0. , 1. , 2. , 3. , 2. , 1. , 4. , 3. ],\n",
       "         [4. , 5. , 6. , 7. , 1. , 2. , 3. , 4. ],\n",
       "         [8. , 9. , 10., 11., 4. , 3. , 2. , 1. ]]))"
      ]
     },
     "metadata": {},
     "execution_count": 23
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "有时，我们想[**通过 *逻辑运算符* 构建二元张量**]。以 `X == Y` 为例子。\n",
    "对于每个位置，如果 `X` 和 `Y` 在该位置相等，则新张量中相应项的值为1，这意味着逻辑语句 `X == Y` 在该位置处为真，否则该位置为 0。\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "source": [
    "X == Y"
   ],
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "Tensor(shape=[3, 4], dtype=bool, place=CPUPlace, stop_gradient=True,\n",
       "       [[False, True , False, True ],\n",
       "        [False, False, False, False],\n",
       "        [False, False, False, False]])"
      ]
     },
     "metadata": {},
     "execution_count": 24
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "[**对张量中的所有元素进行求和会产生一个只有一个元素的张量。**]\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "source": [
    "X.sum()"
   ],
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "Tensor(shape=[1], dtype=float32, place=CPUPlace, stop_gradient=True,\n",
       "       [66.])"
      ]
     },
     "metadata": {},
     "execution_count": 25
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 2.1.3. 广播机制\n",
    ":label:`subsec_broadcasting`\n",
    "\n",
    "在上面的部分中，我们看到了如何在相同形状的两个张量上执行按元素操作。在某些情况下，[**即使形状不同，我们仍然可以通过调用 *广播机制* （broadcasting mechanism） 来执行按元素操作**]。这种机制的工作方式如下：首先，通过适当复制元素来扩展一个或两个数组，以便在转换之后，两个张量具有相同的形状。其次，对生成的数组执行按元素操作。\n",
    "\n",
    "在大多数情况下，我们将沿着数组中长度为1的轴进行广播，如下例子：\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "source": [
    "a = paddle.reshape(paddle.arange(3), (3, 1))\n",
    "b = paddle.reshape(paddle.arange(2), (1, 2))\n",
    "a, b"
   ],
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "(Tensor(shape=[3, 1], dtype=int64, place=CPUPlace, stop_gradient=True,\n",
       "        [[0],\n",
       "         [1],\n",
       "         [2]]),\n",
       " Tensor(shape=[1, 2], dtype=int64, place=CPUPlace, stop_gradient=True,\n",
       "        [[0, 1]]))"
      ]
     },
     "metadata": {},
     "execution_count": 26
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "由于 `a` 和 `b` 分别是 $3\\times1$ 和 $1\\times2$ 矩阵，如果我们让它们相加，它们的形状不匹配。我们将两个矩阵*广播*为一个更大的 $3\\times2$ 矩阵，如下所示：矩阵 `a`将复制列，矩阵 `b`将复制行，然后再按元素相加。\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "source": [
    "a + b"
   ],
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "Tensor(shape=[3, 2], dtype=int64, place=CPUPlace, stop_gradient=True,\n",
       "       [[0, 1],\n",
       "        [1, 2],\n",
       "        [2, 3]])"
      ]
     },
     "metadata": {},
     "execution_count": 27
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 2.1.4. 索引和切片\n",
    "\n",
    "就像在任何其他 Python 数组中一样，张量中的元素可以通过索引访问。与任何 Python 数组一样：第一个元素的索引是 0；可以指定范围以包含第一个元素和最后一个之前的元素。与标准 Python 列表一样，我们可以通过使用负索引根据元素到列表尾部的相对位置访问元素。\n",
    "\n",
    "因此，我们[**可以用 `[-1]` 选择最后一个元素，可以用 `[1:3]` 选择第二个和第三个元素**]，如下所示：\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "source": [
    "X[-1], X[1:3]"
   ],
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "(Tensor(shape=[4], dtype=float32, place=CPUPlace, stop_gradient=True,\n",
       "        [8. , 9. , 10., 11.]),\n",
       " Tensor(shape=[2, 4], dtype=float32, place=CPUPlace, stop_gradient=True,\n",
       "        [[4. , 5. , 6. , 7. ],\n",
       "         [8. , 9. , 10., 11.]]))"
      ]
     },
     "metadata": {},
     "execution_count": 28
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "[**除读取外，我们还可以通过指定索引来将元素写入矩阵。**]\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "source": [
    "X[1, 2] = 9\n",
    "X"
   ],
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "Tensor(shape=[3, 4], dtype=float32, place=CPUPlace, stop_gradient=True,\n",
       "       [[0. , 1. , 2. , 3. ],\n",
       "        [4. , 5. , 9. , 7. ],\n",
       "        [8. , 9. , 10., 11.]])"
      ]
     },
     "metadata": {},
     "execution_count": 29
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "如果我们想[**为多个元素赋值相同的值，我们只需要索引所有元素，然后为它们赋值。**]\n",
    "例如，`[0:2, :]` 访问第1行和第2行，其中 “:” 代表沿轴 1（列）的所有元素。虽然我们讨论的是矩阵的索引，但这也适用于向量和超过2个维度的张量。\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "source": [
    "X[0:2, :] = 12\n",
    "X"
   ],
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "Tensor(shape=[3, 4], dtype=float32, place=CPUPlace, stop_gradient=True,\n",
       "       [[12., 12., 12., 12.],\n",
       "        [12., 12., 12., 12.],\n",
       "        [8. , 9. , 10., 11.]])"
      ]
     },
     "metadata": {},
     "execution_count": 30
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 2.1.5. 节省内存\n",
    "\n",
    "[**运行一些操作可能会导致为新结果分配内存**]。例如，如果我们用 `Y = X + Y`，我们将取消引用 `Y` 指向的张量，而是指向新分配的内存处的张量。\n",
    "\n",
    "在下面的例子中，我们用 Python 的 `id()` 函数演示了这一点，它给我们提供了内存中引用对象的确切地址。运行 `Y = Y + X` 后，我们会发现 `id(Y)` 指向另一个位置。这是因为 Python 首先计算 `Y + X`，为结果分配新的内存，然后使 `Y` 指向内存中的这个新位置。\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "source": [
    "before = id(Y)\n",
    "Y = Y + X\n",
    "id(Y) == before"
   ],
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "False"
      ]
     },
     "metadata": {},
     "execution_count": 31
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "这可能是不可取的，原因有两个：首先，我们不想总是不必要地分配内存。在机器学习中，我们可能有数百兆的参数，并且在一秒内多次更新所有参数。通常情况下，我们希望原地执行这些更新。其次，我们可能通过多个变量指向相同参数。如果我们不原地更新，其他引用仍然会指向旧的内存位置，这样我们的某些代码可能会无意中引用旧的参数。\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "幸运的是，(**执行原地操作**)非常简单。我们可以使用切片表示法将操作的结果分配给先前分配的数组，例如 `Y[:] = <expression>`。为了说明这一点，我们首先创建一个新的矩阵 `Z`，其形状与另一个 `Y` 相同，使用 `zeros_like` 来分配一个全$0$的块。\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "source": [
    "Z = paddle.zeros_like(Y)\n",
    "print('id(Z):', id(Z))\n",
    "Z = X + Y\n",
    "print('id(Z):', id(Z))"
   ],
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "id(Z): 140266892838144\n",
      "id(Z): 140266893117232\n"
     ]
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "[**如果在后续计算中没有重复使用 `X`，我们也可以使用 `X[:] = X + Y` 或 `X += Y` 来减少操作的内存开销。**]\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "source": [
    "before = id(X)\n",
    "X += Y\n",
    "id(X) == before"
   ],
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "False"
      ]
     },
     "metadata": {},
     "execution_count": 33
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 2.1.6. 转换为其他 Python 对象\n",
    "\n",
    "[**转换为 NumPy 张量**]很容易，反之也很容易。转换后的结果不共享内存。\n",
    "这个小的不便实际上是非常重要的：当你在 CPU 或 GPU 上执行操作的时候，如果 Python 的 NumPy 包也希望使用相同的内存块执行其他操作，你不希望停下计算来等它。\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "source": [
    "A = X.numpy()\n",
    "B = paddle.to_tensor(A)\n",
    "type(A), type(B)"
   ],
   "outputs": [
    {
     "output_type": "execute_result",
     "data": {
      "text/plain": [
       "(numpy.ndarray, paddle.VarBase)"
      ]
     },
     "metadata": {},
     "execution_count": 34
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "要(**将大小为1的张量转换为 Python 标量**)，我们可以调用 `item` 函数或 Python 的内置函数。\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "source": [
    "a = paddle.to_tensor([3.5])\n",
    "a, a.item(), float(a), int(a)"
   ],
   "outputs": [
    {
     "output_type": "error",
     "ename": "AttributeError",
     "evalue": "'Tensor' object has no attribute 'item'",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mAttributeError\u001b[0m                            Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-35-ce4f5f8b2bde>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[0ma\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mpaddle\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mto_tensor\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m3.5\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0ma\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0ma\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mitem\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mfloat\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0ma\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0ma\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;31mAttributeError\u001b[0m: 'Tensor' object has no attribute 'item'"
     ]
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 2.1.7. 小结\n",
    "\n",
    "* 深度学习存储和操作数据的主要接口是张量（$n$维数组）。它提供了各种功能，包括基本数学运算、广播、索引、切片、内存节省和转换其他 Python 对象。\n",
    "\n",
    "## 2.1.8. 练习\n",
    "\n",
    "1. 运行本节中的代码。将本节中的条件语句 `X == Y` 更改为 `X < Y` 或 `X > Y`，然后看看你可以得到什么样的张量。\n",
    "1. 用其他形状（例如三维张量）替换广播机制中按元素操作的两个张量。结果是否与预期相同？\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "[Discussions](https://discuss.d2l.ai/t/1747)\n"
   ],
   "metadata": {}
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "pp200",
   "language": "python",
   "name": "pp200"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}